var blocksPerTile = 16;
var pixelsPerBlock = 16;
var inversePixelsPerBlock = 1 / pixelsPerBlock;

var mapPixelWidth = mapBlockWidth * pixelsPerBlock;
var mapPixelHeight = mapBlockHeight * pixelsPerBlock;

var mapTileWidth = mapBlockWidth / 16;
var mapTileHeight = mapBlockHeight / 16;

// 6 zoom levels, 2^5 = 32.
var zoomScaling = 32;
var inverseZoomScaling = 1 / zoomScaling;

// We need the map dimensions at the lowest zoom level as the base for
// world coordinates.
var baseZoomWidth = mapPixelWidth * inverseZoomScaling;
var baseZoomHeight = mapPixelHeight * inverseZoomScaling;

// Constant value. Do not change.
var tileSize = 256;

///////////////////////////////////////////////////////////////////
// Utility Functions
///////////////////////////////////////////////////////////////////
function bound(value, optionalMin, optionalMax) {
	if (optionalMin != null)
	{
		value = Math.max(value,optionalMin);
	}

	if (optionalMax != null)
	{
		value = Math.min(value, optionalMax);
	}

	return value;
}

function degreesToRadians(deg) {
	return deg * (Math.PI / 180);
}

function radiansToDegrees(rad) {
	return rad / (Math.PI / 180);
}

///////////////////////////////////////////////////////////////////
// TerrariaProjection
///////////////////////////////////////////////////////////////////

// This project allows the conversion from Terraria to an artificial lat/lng
// and back for the google maps API.
function TerrariaProjection() {
	this.worldOrigin = new google.maps.Point(baseZoomWidth / 2, baseZoomHeight / 2);
	this.worldCoordinatePerLonDegree = baseZoomWidth / 360;
	this.worldCoordinateLatRange = baseZoomHeight / 2;
}

TerrariaProjection.prototype.fromBlockToPoint = function(blockPoint) {
	return new google.maps.Point(blockPoint.x * pixelsPerBlock * inverseZoomScaling, blockPoint.y * pixelsPerBlock * inverseZoomScaling);
}

TerrariaProjection.prototype.fromPointToBlock = function(blockPoint) {
	return new google.maps.Point(blockPoint.x * inversePixelsPerBlock * zoomScaling, blockPoint.y * inversePixelsPerBlock * zoomScaling);
}

TerrariaProjection.prototype.fromLatLngToPoint = function(latLng, optionalPoint) {
	var origin = this.worldOrigin;
	var x = origin.x + this.worldCoordinatePerLonDegree * latLng.lng();
	var latRadians = degreesToRadians(latLng.lat());
	var y = origin.y - this.worldCoordinateLatRange * Math.sin(latRadians);
	return new google.maps.Point(x, y); 
};

TerrariaProjection.prototype.fromPointToLatLng = function(point, noWrap) {
	var y = point.y;
	var x = point.x;
	
	if (y < 0) {
		y = 0;
	}

	if (y >= baseZoomHeight) {
		y = baseZoomHeight;
	}
	
	var origin = this.worldOrigin;
	var lng = (x - origin.x) / this.worldCoordinatePerLonDegree;
	var latRadians = Math.asin((origin.y - y) / this.worldCoordinateLatRange);
	var lat = radiansToDegrees(latRadians);
	return new google.maps.LatLng(lat, lng, noWrap); 
};

TerrariaProjection.prototype.fromBlockToLatLng = function(blockPoint) {
	return this.fromPointToLatLng(this.fromBlockToPoint(blockPoint));
}

TerrariaProjection.prototype.fromLatLngToBlock = function(blockPoint) {
	return this.fromPointToBlock(this.fromLatLngToPoint(blockPoint));
}

// The projection for coordinate translations.
var projection = new TerrariaProjection();

///////////////////////////////////////////////////////////////////
// TerrariaMapType
///////////////////////////////////////////////////////////////////

// The map type for the Terraria Map
function TerrariaMapType() { 
} 

TerrariaMapType.prototype.name = "Terraria"; 
TerrariaMapType.prototype.alt = "Tile Coordinate Map Type"; 
TerrariaMapType.prototype.tileSize = new google.maps.Size(tileSize, tileSize); 
TerrariaMapType.prototype.minZoom = 0; 
TerrariaMapType.prototype.maxZoom = 5; 
TerrariaMapType.prototype.isPng = true;
TerrariaMapType.prototype.projection = projection;

TerrariaMapType.prototype.getTile = function(coord, zoom, ownerDocument) {
	var zoomLevel;

	switch (zoom) {
		case 0:
			zoomLevel = 0.03125;
			break;

		case 1:
			zoomLevel = 0.0625;
			break;

		case 2:
			zoomLevel = 0.125;
			break;

		case 3:
			zoomLevel = 0.25;
			break;

		case 4:
			zoomLevel = 0.5;
			break;

		case 5:
			zoomLevel = 1;
			break;

		case 6:
			zoomLevel = 2;
			break;
	}

	var div = ownerDocument.createElement('DIV'); 
	div.style.background = "black url(" + basePath + "Zoom%20" + zoomLevel.toString() + "/Layer%200/" + coord.x.toString() + "/" + coord.y.toString() + ".png)";

	//div.innerHTML = coord + "," + zoomLevel.toString() + "<br/><br/>Zoom " + zoomLevel.toString() + "/Layer 0/" + coord.x.toString() + "/" + coord.y.toString() + ".png)";
	//div.style.border = "1px solid gray";
	div.style.color = "white";
	div.style.width = this.tileSize.width + 'px'; 
	div.style.height = this.tileSize.height + 'px'; 
	div.style.fontSize = '10';   
	return div; 
}; 

///////////////////////////////////////////////////////////////////
// MapObject
///////////////////////////////////////////////////////////////////

function MapObject(location) {
	this.location = location;
}

function Npc(location, name) {
	this.location = location;
	this.name = name;
}

function Item(name, count) {
	this.name = name;
	this.count = count;
}

function Chest(location) {
	this.location = location;
	this.contents = new Array();
	this.type = 0;
}

Chest.prototype.onClick = function() {
	var contentsBubble = new google.maps.InfoWindow();
	
	var contentsTable = "<table class='Chest'><tr><th class='Item'>Item</th><th class='Count'>#</th></tr>";

	$(this.contents).sort(function(a, b) {
		return a.name.localeCompare(b.name);
	});

	$.each(this.contents, function() {
		contentsTable += "<tr><td class='Item'>" + this.name + "</td><td class='Count'>" + this.count + "</td></tr>";
	});
	
	contentsTable += "</table>";

	contentsBubble.setContent("<span style='font-family: verdana'>Chest</span><br/><br/>" + contentsTable);
	contentsBubble.setPosition(this.location);
	ShowInfoWindow(contentsBubble);
}

Chest.prototype.getMarkerIcon = function() {
	switch (this.type) {
		case 0:
			return "Images/NormalChestMarker.png";

		case 1:
			return "Images/GoldChestMarker.png";

		case 2:
			return "Images/LockedGoldChestMarker.png";

		case 3:
			return "Images/ShadowChestMarker.png";

		case 4:
			return "Images/LockedShadowChestMarker.png";

		case 5:
			return "Images/BarrelMarker.png";

		case 6:
			return "Images/TrashCanMarker.png";
	}
}

function Sign(location, text) {
	this.location = location;
	this.text = text;
}

Sign.prototype.onClick = function() {
	var contentsBubble = new google.maps.InfoWindow();
	contentsBubble.setContent("<p style='font-family: verdana'>" + this.text + "</p>");
	contentsBubble.setPosition(this.location);
	ShowInfoWindow(contentsBubble);
}

function Banner(location, type) {
	this.location = location;
	this.type = type;
}

Banner.prototype.getMarkerIcon = function() {
	switch (this.type) {
		case 0:
			return "Images/RedBannerMarker.png";

		case 1:
			return "Images/GreenBannerMarker.png";

		case 2:
			return "Images/BlueBannerMarker.png";

		case 3:
			return "Images/YellowBannerMarker.png";
	}
}

///////////////////////////////////////////////////////////////////
// Info Windows
///////////////////////////////////////////////////////////////////

var activeInfoWindow = null;

function ShowInfoWindow(infoWindow) {
	if (activeInfoWindow != null) {
		activeInfoWindow.close();
	}

	activeInfoWindow = infoWindow;
	
	if (activeInfoWindow != null) {
		infoWindow.open(map);
	}
}

///////////////////////////////////////////////////////////////////
// Controls
///////////////////////////////////////////////////////////////////

function GoToSpawnControl(map) {
	// Set CSS for the control border
	var controlUI = document.createElement('DIV');
	controlUI.style.backgroundColor = '#CCDDFF';
	controlUI.style.borderStyle = 'solid';
	controlUI.style.borderWidth = '1px';
	controlUI.style.cursor = 'pointer';
	controlUI.style.textAlign = 'center';
	controlUI.style.margin = '3px';
	controlUI.title = 'Spawn Point';

	// Set CSS for the control interior
	var controlText = document.createElement('DIV');
	controlText.style.fontFamily = 'Verdana,sans-serif';
	controlText.style.fontSize = '12px';
	controlText.style.padding = '4px';
	controlText.innerHTML = 'Go to Spawn Point';
	controlUI.appendChild(controlText);

	google.maps.event.addDomListener(controlUI, 'click', function() {
		ShowInfoWindow(spawnPointInfoWindow);
	});

	this.element = controlUI;
}

function ToggleControl(map, markerType, name) {
	var text = markerType;
	
	if (name != null) {
		text = name;
	}

	// Set CSS for the control border
	var controlUI = document.createElement('DIV');
	controlUI.style.backgroundColor = '#7799DD';
	controlUI.style.borderStyle = 'solid';
	controlUI.style.borderWidth = '1px';
	controlUI.style.cursor = 'pointer';
	controlUI.style.textAlign = 'center';
	controlUI.style.width = '120px';
	controlUI.style.margin = '3px';

	// Set CSS for the control interior
	var controlText = document.createElement('DIV');
	controlText.style.fontFamily = 'Verdana,sans-serif';
	controlText.style.fontSize = '12px';
	controlText.style.padding = '4px';
	controlText.innerHTML = text;
	controlUI.appendChild(controlText);

	var self = this;

	google.maps.event.addDomListener(controlUI, 'click', function() {
		self.visible = !self.visible;
		var markerVisible = self.visible;
		
		if (self.visible) {
			self.element.style.backgroundColor = '#CCDDFF';
		} else {
			self.element.style.backgroundColor = '#7799DD';
		}

		$.each(mapObjects[markerType], function(mapObject) {
			this.marker.setVisible( markerVisible );
		});
	});
	
	this.label = controlText;
	this.text = text;
	this.element = controlUI;
	this.visible = false;
}

///////////////////////////////////////////////////////////////////
// Initialization
///////////////////////////////////////////////////////////////////

var map;
var spawnPoint = projection.fromBlockToLatLng(new google.maps.Point(2102, 180)); 
var spawnPointInfoWindow = new google.maps.InfoWindow();
var terrariaMapType = new TerrariaMapType();

var mapObjects = {};
mapObjects['Npc'] = new Array();
mapObjects['Chest'] = new Array();
mapObjects['Sign'] = new Array();
mapObjects['HeartContainer'] = new Array();
mapObjects['Anvil'] = new Array();
mapObjects['Forge'] = new Array();
mapObjects['Workbench'] = new Array();
mapObjects['ShadowOrb'] = new Array();
mapObjects['HellForge'] = new Array();
mapObjects['Sawmill'] = new Array();
mapObjects['Banner'] = new Array();
mapObjects['Altar'] = new Array();
mapObjects['Bed'] = new Array();

var mapObjectImages = {};
mapObjectImages['Npc'] = "Images/NpcMarker.png";
mapObjectImages['Chest'] = "Images/NormalChestMarker.png";
mapObjectImages['Sign'] = "Images/SignMarker.png";
mapObjectImages['HeartContainer'] = "Images/HeartContainerMarker.png";
mapObjectImages['Anvil'] = "Images/AnvilMarker.png";
mapObjectImages['Forge'] = "Images/ForgeMarker.png";
mapObjectImages['Workbench'] = "Images/WorkbenchMarker.png";
mapObjectImages['ShadowOrb'] = "Images/ShadowOrbMarker.png";
mapObjectImages['HellForge'] = "Images/HellForgeMarker.png";
mapObjectImages['Sawmill'] = "Images/SawmillMarker.png";
mapObjectImages['Banner'] = "Images/BlueBannerMarker.png";
mapObjectImages['Altar'] = "Images/AltarMarker.png";
mapObjectImages['Bed'] = "Images/BedMarker.png";

function parsePosition(blockPosition) {
	var parts = blockPosition.split(",", 2);
	var x = parseInt(parts[0]);
	var y = parseInt(parts[1]);

	return new google.maps.Point(x, y);
}

function parsePixelPositionToLatLng(pixelPosition, offset) {
	var blockPosition = parsePosition(pixelPosition);

	// Convert from pixel to block.
	blockPosition.x /= 16;
	blockPosition.y /= 16;

	if (offset != null) {
		blockPosition.x += offset.x;
		blockPosition.y += offset.y;
	}

	return projection.fromBlockToLatLng(blockPosition, true);
}

function parsePositionToLatLng(blockPosition, offset) {
	var blockPosition = parsePosition(blockPosition);

	if (offset != null) {
		blockPosition.x += offset.x;
		blockPosition.y += offset.y;
	}

	return projection.fromBlockToLatLng(blockPosition, true);
}

function createSpawnPointInfoPanel() {
	return '<span style="font-family: verdana">Spawn Point</span>';
}

function loadMapData(xml) {
	var world = $(xml).find('World');

	spawnPoint = parsePositionToLatLng(world.attr('Spawn'));

	$(xml).find('Npc').each(loadNpc);
	$(xml).find('Chest').each(loadChest);
	$(xml).find('Sign').each(loadSign);
	$(xml).find('HeartContainer').each(loadHeartContainer);
	$(xml).find('Anvil').each(loadAnvil);
	$(xml).find('Forge').each(loadForge);
	$(xml).find('Workbench').each(loadWorkbench);
	$(xml).find('ShadowOrb').each(loadShadowOrb);
	$(xml).find('HellForge').each(loadHellForge);
	$(xml).find('Sawmill').each(loadSawmill);
	$(xml).find('Banner').each(loadBanner);
	$(xml).find('Altar').each(loadAltar);
	$(xml).find('Bed').each(loadBed);

	spawnPointInfoWindow.setContent(createSpawnPointInfoPanel());
	spawnPointInfoWindow.setPosition(spawnPoint);
	ShowInfoWindow(spawnPointInfoWindow);
	
	map.setCenter(spawnPoint);

	$.each(mapObjects, function(index, value) {
		$.each(value, function(mapObject) {
			var self = this;
			var markerImage = mapObjectImages[index];
			
			if (this.getMarkerIcon != null) {
				markerImage = this.getMarkerIcon();
			}

			self.marker = new google.maps.Marker({
				position: self.location, 
				map: map,
				title: index,
				visible: false,
				icon: basePath + markerImage
			});

			if (self.onClick != null) {
				var marker = self.marker;

				google.maps.event.addListener(marker, 'click', function() {
					self.onClick();
				});
			}
		});
	});
}

function loadNpc() {
	var npc = new Npc(parsePixelPositionToLatLng($(this).attr('Location')), $(this).attr('Name'));

	mapObjects['Npc'].push(npc);
}

function loadChest() {
	var chest = new Chest(parsePositionToLatLng($(this).attr('Location'), new google.maps.Point(1, 0)));
	
	chest.type = parseInt($(this).attr('Type'));

	$(this).find('Item').each(function() {
		chest.contents.push(new Item($(this).attr('Name'), parseInt($(this).attr('Count'))));
	});
	
	chest.contents.sort( function(a, b) {
		return a.name.localeCompare(b.name);
	});

	mapObjects['Chest'].push(chest);
}

function loadSign() {
	var sign = new Sign(parsePositionToLatLng($(this).attr('Location'), new google.maps.Point(1, 0)), $(this).text());
	mapObjects['Sign'].push(sign);
}

function loadHeartContainer() {
	mapObjects['HeartContainer'].push(new MapObject(parsePositionToLatLng($(this).attr('Location'), new google.maps.Point(1, 0))));
}

function loadAnvil() {
	mapObjects['Anvil'].push(new MapObject(parsePositionToLatLng($(this).attr('Location'), new google.maps.Point(1, 0))));
}

function loadForge() {
	mapObjects['Forge'].push(new MapObject(parsePositionToLatLng($(this).attr('Location'), new google.maps.Point(1.5, 0))));
}

function loadWorkbench() {
	mapObjects['Workbench'].push(new MapObject(parsePositionToLatLng($(this).attr('Location'), new google.maps.Point(1, 0))));
}

function loadShadowOrb() {
	mapObjects['ShadowOrb'].push(new MapObject(parsePositionToLatLng($(this).attr('Location'), new google.maps.Point(1, 0))));
}

function loadHellForge() {
	mapObjects['HellForge'].push(new MapObject(parsePositionToLatLng($(this).attr('Location'), new google.maps.Point(1.5, 0))));
}

function loadSawmill() {
	mapObjects['Sawmill'].push(new MapObject(parsePositionToLatLng($(this).attr('Location'), new google.maps.Point(1.5, 0))));
}

function loadBanner() {
	var banner = new Banner(parsePositionToLatLng($(this).attr('Location'), new google.maps.Point(0.5, 0)), parseInt($(this).attr('Type')));
	mapObjects['Banner'].push(banner);
}

function loadAltar() {
	mapObjects['Altar'].push(new MapObject(parsePositionToLatLng($(this).attr('Location'), new google.maps.Point(1.5, 0))));
}

function loadBed() {
	mapObjects['Bed'].push(new MapObject(parsePositionToLatLng($(this).attr('Location'), new google.maps.Point(2, 0))));
}

function initialize() {
	var mapOptions = { 
		zoom: 6, 
		mapTypeControlOptions: { 
			mapTypeIds: ['Terraria'], 
			style: google.maps.MapTypeControlStyle.HORIZONTAL_BAR 
		} 
	}; 

	map = new google.maps.Map(document.getElementById("map_canvas"), mapOptions); 
	map.mapTypes.set('terraria', terrariaMapType); 
	map.setMapTypeId('terraria'); 
	//map.setZoom(1);
	
	var controlDiv = document.createElement('DIV');
	controlDiv.style.padding = '5px';

	var goToSpawnControl = new GoToSpawnControl(map);
	var altarToggleControl = new ToggleControl(map, 'Altar', 'Altars');
	var anvilToggleControl = new ToggleControl(map, 'Anvil', 'Anvils');
	var bannersToggleControl = new ToggleControl(map, 'Banner', 'Banners');
	var bedToggleControl = new ToggleControl(map, 'Bed', 'Beds');
	var chestToggleControl = new ToggleControl(map, 'Chest', 'Chests');
	var forgeToggleControl = new ToggleControl(map, 'Forge', 'Forges');
	var heartContainerToggleControl = new ToggleControl(map, 'HeartContainer', 'Heart Containers');
	var hellForgeToggleControl = new ToggleControl(map, 'HellForge', 'Hell Forges');
	var npcToggleControl = new ToggleControl(map, 'Npc', 'NPCs');
	var sawmillToggleControl = new ToggleControl(map, 'Sawmill', 'Sawmills');
	var shadowOrbToggleControl = new ToggleControl(map, 'ShadowOrb', 'Shadow Orbs');
	var signToggleControl = new ToggleControl(map, 'Sign', 'Signs');
	var workbenchToggleControl = new ToggleControl(map, 'Workbench', 'Workbenches');
	
	controlDiv.appendChild(goToSpawnControl.element);
	controlDiv.appendChild(document.createElement('BR'));
	controlDiv.appendChild(altarToggleControl.element);
	controlDiv.appendChild(anvilToggleControl.element);
	controlDiv.appendChild(bannersToggleControl.element);
	controlDiv.appendChild(bedToggleControl.element);
	controlDiv.appendChild(chestToggleControl.element);
	controlDiv.appendChild(forgeToggleControl.element);
	controlDiv.appendChild(heartContainerToggleControl.element);
	controlDiv.appendChild(hellForgeToggleControl.element);
	controlDiv.appendChild(npcToggleControl.element);
	controlDiv.appendChild(sawmillToggleControl.element);
	controlDiv.appendChild(shadowOrbToggleControl.element);
	controlDiv.appendChild(signToggleControl.element);
	controlDiv.appendChild(workbenchToggleControl.element);

	map.controls[google.maps.ControlPosition.TOP_RIGHT].push(controlDiv);

	// Load the map data file.
	$.ajax({
		type: "GET",
		url: basePath + "MapData.xml",
		dataType: "xml",
		success: loadMapData
	});
}

